﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Collections.ObjectModel;

namespace System.Linq.Expressions
{
	public static class FindExtensions
	{

		/// <summary>
		/// Procura pelo tipo da expressão na árvore <paramref name="node"/>
		/// </summary>
		/// <typeparam name="TExpression"></typeparam>
		/// <param name="node"></param>
		/// <returns></returns>
		public static TExpression Find<TExpression>( this Expression node )
			where TExpression : Expression
		{
			return node.Find<TExpression>( ( Func<TExpression, bool> )null );
		}

		/// <summary>
		/// Procura pelo tipo da expressão na árvore <paramref name="node"/> que satifaz 
		/// a condição <paramref name="condition"/>
		/// </summary>
		/// <typeparam name="TExpression"></typeparam>
		/// <param name="node"></param>
		/// <param name="condition"></param>
		/// <returns></returns>
		public static TExpression Find<TExpression>( this Expression node, Func<TExpression, bool> condition )
			where TExpression : Expression
		{
			var finder = new FindExpression<TExpression>( condition );
			return finder.Search( node );
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="node"></param>
		/// <returns></returns>
		public static Expression FindRoot( this Expression node )
		{
			Expression last=null;
			node.Find<Expression>(
				x =>
				{
					last = x;
					return false;
				} );
			return last;
		}

		/// <summary>
		/// Verifica se existe o nodo do tipo <paramref name="lookingFor"/> na árvore
		/// </summary>
		/// <param name="node"></param>
		/// <param name="lookingFor"></param>
		/// <returns></returns>
		public static bool Has( this Expression node, ExpressionType lookingFor )
		{
			return node.Find<Expression>( x => x.NodeType == lookingFor ) != null;
		}

		public static bool Has<TExpression>( this Expression node )
			where TExpression : Expression
		{
			return node.Has<TExpression>( ( Func<TExpression, bool> )null );
		}

		public static bool Has<TExpression>( this Expression node, Func<TExpression, bool> predicate )
			where TExpression : Expression
		{
			return node.Find<TExpression>( predicate ) != null;
		}


		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="TExpression"></typeparam>
		/// <param name="node"></param>
		/// <param name="predicate"></param>
		/// <returns></returns>
		public static bool Is<TExpression>( this Expression node, Func<TExpression, bool> predicate )
			where TExpression : Expression
		{
			if ( node is TExpression )
				return predicate == null || predicate( ( TExpression )node );
			else
				return false;
		}
	}

	/// <summary>
	/// Procura por uma expressão
	/// </summary>
	/// <typeparam name="TExpression"></typeparam>
	internal sealed class FindExpression<TExpression> : ExpressionVisitor
		where TExpression : Expression
	{
		private Func<TExpression, bool> condition;

		public FindExpression( Func<TExpression, bool> predicate )
		{
			this.condition = predicate ?? this.ReturnAll;
		}

		/// <summary>
		/// Obtém a expressão que foi encontrada
		/// </summary>
		public TExpression ExpressionFound
		{
			get;
			private set;
		}

		/// <summary>
		/// Procura pela expressão
		/// </summary>
		/// <param name="exp"></param>
		/// <returns></returns>
		public TExpression Search(Expression exp)
		{
			this.ExpressionFound = null;
			this.Visit( exp );
			return this.ExpressionFound;
		}

		protected override Expression VisitLambda<T>( Expression<T> node )
		{
			var body = base.Visit( node.Body );
			var parameters = node.Parameters.Select( p => base.Visit( p ) );
			return Expression.Lambda<T>( body, parameters.Cast<ParameterExpression>().ToArray() );
		}

		protected override Expression VisitBinary( BinaryExpression node )
		{
			if ( node is TExpression )
				if ( this.condition( node as TExpression ) )
					return this.ExpressionFound = node as TExpression;
			return base.VisitBinary( node );
		}

		public override Expression Visit( Expression node )
		{
			if ( this.ExpressionFound == null
				&& node is TExpression )
			{
				if ( this.condition( ( TExpression )node ) )
				{
					this.ExpressionFound = ( TExpression )node;
				}
			}

			return base.Visit( node );
			//if ( this.ExpressionFound == null )
			//	return base.Visit( node );
			//else
			//	return this.ExpressionFound;
		}

		private bool ReturnAll( TExpression exp )
		{
			return true;
		}
	}
}
